// $Id: sessions.c 94 2011-10-19 12:48:16Z cedric.shih@gmail.com $
/*
 * Copyright 2011 Cedric Shih (cedric dot shih at gmail dot com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>

#include <zcbuffer.h>

#include "log.h"
#include "codec/request.h"

#include "sessions.h"

#define BUFFER_SIZE 512 << 10

struct niortsp_sessions *niortsp_sessions_new(void)
{
	struct niortsp_sessions *sessions;

	if (!(sessions = (struct niortsp_sessions *)calloc(
			1, sizeof(*sessions)))) {
		niortsp_error("calloc: %s", strerror(errno));
		goto finally;
	}

	TAILQ_INIT(&sessions->list);
	RB_INIT(&sessions->tree);

finally:
	return sessions;
}

void niortsp_sessions_free(struct niortsp_sessions *sessions)
{
	if (!sessions) {
		return;
	}

	niortsp_sessions_clear(sessions);
	free(sessions);
}

struct niortsp_session *niortsp_sessions_find(
		struct niortsp_sessions *sessions, int fd)
{
	struct niortsp_session find;

	niortsp_debug("finding session: %d", fd);

	find.fd = fd;
	return RB_FIND(niortsp_session_tree, &sessions->tree, &find);
}

int niortsp_sessions_add(struct niortsp_sessions *sessions,
		struct niortsp_session **out)
{
	int rc;
	struct niortsp_session *session;

	niortsp_debug("adding session");

	if (!(session = (struct niortsp_session *)
			calloc(1, sizeof(*session)))) {
		rc = errno ? errno : -1;
		niortsp_error("calloc: %s", strerror(rc));
		goto finally;
	}

	TAILQ_INIT(&session->streams);

	if (!(session->decoder = niortsp_request_decoder_new())) {
		rc = errno ? errno : -1;
		niortsp_error("niortsp_request_decoder_new: %s",
				strerror(rc));
		goto finally;
	}

	if (!(session->input_buffer = zcbuffer_new(BUFFER_SIZE))) {
		rc = errno ? errno : -1;
		niortsp_error("zcbuffer_new: %s", strerror(rc));
		goto finally;
	}

	if (!(session->output_buffer = zcbuffer_new(BUFFER_SIZE))) {
		rc = errno ? errno : -1;
		niortsp_error("zcbuffer_new: %s", strerror(rc));
		goto finally;
	}

	if (!(session->request_buffer = zcbuffer_new(BUFFER_SIZE))) {
		rc = errno ? errno : -1;
		niortsp_error("zcbuffer_new: %s", strerror(rc));
		goto finally;
	}

	RB_INSERT(niortsp_session_tree, &sessions->tree, session);
	TAILQ_INSERT_TAIL(&sessions->list, &session->item, next);
	*out = session;
	rc = 0;

finally:
	return rc;
}

void niortsp_sessions_remove(struct niortsp_sessions *sessions,
		struct niortsp_session *session)
{
	niortsp_debug("removing session: %s:%d",
			inet_ntoa(session->remote.sin_addr),
			ntohs(session->remote.sin_port));

	TAILQ_REMOVE(&sessions->list, &session->item, next);
	RB_REMOVE(niortsp_session_tree, &sessions->tree, session);

	if (session->request_buffer) {
		zcbuffer_free(session->request_buffer);
	}

	if (session->output_buffer) {
		zcbuffer_free(session->output_buffer);
	}

	if (session->input_buffer) {
		zcbuffer_free(session->input_buffer);
	}

	if (session->decoder) {
		niortsp_request_decoder_free(session->decoder);
	}

	niortsp_stream_list_clear(&session->streams);
	free(session);
}

void niortsp_sessions_clear(struct niortsp_sessions *sessions)
{
	struct niortsp_session *entry;

	while ((entry = RB_MIN(niortsp_session_tree, &sessions->tree))) {
		niortsp_sessions_remove(sessions, entry);
	}
}

int niortsp_session_compare(struct niortsp_session *a,
		struct niortsp_session *b)
{
	return a->fd - b->fd;
}

RB_GENERATE(niortsp_session_tree, niortsp_session, entry,
		niortsp_session_compare);
